window._nma = (function(window, document, monitorConf) {
  const JS_ERROR = "JS_ERROR";
  const RESOURCE_LOAD = "RESOURCE_LOAD";
  const HTTP_LOG = "HTTP_LOG";
  const CUSTOMER_PV = "CUSTOMER_PV";
  const LOAD_PAGE = "LOAD_PAGE";
  const ELE_BEHAVIOR = "ELE_BEHAVIOR";
  const CUSTOMIZE_BEHAVIOR = "CUSTOMIZE_BEHAVIOR";
  const WEB_LOCATION = window.location.href;
  const HTTP_UPLOAD_LOG_API = "/upLog";
  const version = "0.0.5";
  const MONITOR_ID = monitorConf && monitorConf.monitor_id;
  const utils = new MonitorUtils();
  const DEVICE_INFO = utils.getDevice();
  const user_id = monitorConf && monitorConf.user_id;

  console.log(user_id);
  if (user_id) {
    window.user_lbn_jsonpResponse = function(e) {
      if (e.code == 200) {
        localStorage.NNWID = e.data.token;
      }
      console.log(e, "user_lbn_jsonpResponse");
    };
    utils.getNNWUserid();
  }

  const timingObj = performance && performance.timing;

  // 获取页面加载的具体属性
  const resourcesObj = (function() {
    if (performance && typeof performance.getEntries === "function") {
      return performance.getEntries();
    }
    return null;
  })();

  // console.log(resourcesObj, timingObj);

  let defaultLocation = WEB_LOCATION.split("?")[0].replace("#", "");

  /**
   * @description: 监控代码需要的工具类
   */
  function MonitorUtils() {
    this.getNNWUserid = () => {
      console.log("getNNWID");
      const e = document.createElement("script");
      e.src = "//u.jss.com.cn/u/v1/auth/check_login";
      const t = document.getElementsByTagName("script")[0];
      t.parentNode.insertBefore(e, t);
    };
    /**
     * @description:  返回设备信息
     */
    this.getDevice = function() {
      const device = {};
      const ua = navigator.userAgent;
      const android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
      const ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
      const ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
      const iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
      const mobileInfo = ua.match(/Android\s[\S\s]+Build\//);
      device.ios = device.android = device.iphone = device.ipad = device.androidChrome = false;
      device.isWeixin = /MicroMessenger/i.test(ua);
      device.os = "web";
      device.deviceName = "PC";
      // Android
      if (android) {
        device.os = "android";
        device.osVersion = android[2];
        device.android = true;
        device.androidChrome = ua.toLowerCase().indexOf("chrome") >= 0;
      }
      if (ipad || iphone || ipod) {
        device.os = "ios";
        device.ios = true;
      }
      // iOS
      if (iphone && !ipod) {
        device.osVersion = iphone[2].replace(/_/g, ".");
        device.iphone = true;
      }
      if (ipad) {
        device.osVersion = ipad[2].replace(/_/g, ".");
        device.ipad = true;
      }
      if (ipod) {
        device.osVersion = ipod[3] ? ipod[3].replace(/_/g, ".") : null;
        device.iphone = true;
      }
      // iOS 8+ changed UA
      if (device.ios && device.osVersion && ua.indexOf("Version/") >= 0) {
        if (device.osVersion.split(".")[0] === "10") {
          device.osVersion = ua
            .toLowerCase()
            .split("version/")[1]
            .split(" ")[0];
        }
      }

      // 如果是ios, deviceName 就设置为iphone，根据分辨率区别型号
      if (device.iphone) {
        device.deviceName = "iphone";
        const screenWidth = window.screen.width;
        const screenHeight = window.screen.height;
        if (screenWidth === 320 && screenHeight === 480) {
          device.deviceName = "iphone 4";
        } else if (screenWidth === 320 && screenHeight === 568) {
          device.deviceName = "iphone 5/SE";
        } else if (screenWidth === 375 && screenHeight === 667) {
          device.deviceName = "iphone 6/7/8";
        } else if (screenWidth === 414 && screenHeight === 736) {
          device.deviceName = "iphone 6/7/8 Plus";
        } else if (screenWidth === 375 && screenHeight === 812) {
          device.deviceName = "iphone X/S/Max";
        }
      } else if (device.ipad) {
        device.deviceName = "ipad";
      } else if (mobileInfo) {
        const info = mobileInfo[0];
        const deviceName = info.split(";")[1].replace(/Build\//g, "");
        device.deviceName = deviceName.replace(/(^\s*)|(\s*$)/g, "");
      }
      // 浏览器模式, 获取浏览器信息
      // TODO 需要补充更多的浏览器类型进来
      if (ua.indexOf("Mobile") == -1) {
        const agent = navigator.userAgent.toLowerCase();
        const regStr_ie = /msie [\d.]+;/gi;
        const regStr_ff = /firefox\/[\d.]+/gi;
        const regStr_chrome = /chrome\/[\d.]+/gi;
        const regStr_saf = /safari\/[\d.]+/gi;

        device.browserName = "未知";
        //IE
        if (agent.indexOf("msie") > 0) {
          const browserInfo = agent.match(regStr_ie)[0];
          device.browserName = browserInfo.split("/")[0];
          device.browserVersion = browserInfo.split("/")[1];
        }
        //firefox
        if (agent.indexOf("firefox") > 0) {
          const browserInfo = agent.match(regStr_ff)[0];
          device.browserName = browserInfo.split("/")[0];
          device.browserVersion = browserInfo.split("/")[1];
        }
        //Safari
        if (agent.indexOf("safari") > 0 && agent.indexOf("chrome") < 0) {
          const browserInfo = agent.match(regStr_saf)[0];
          device.browserName = browserInfo.split("/")[0];
          device.browserVersion = browserInfo.split("/")[1];
        }
        //Chrome
        if (agent.indexOf("chrome") > 0) {
          const browserInfo = agent.match(regStr_chrome)[0];
          device.browserName = browserInfo.split("/")[0];
          device.browserVersion = browserInfo.split("/")[1];
        }
      }
      // Webview
      device.webView =
        (iphone || ipad || ipod) && ua.match(/.*AppleWebKit(?!.*Safari)/i);

      // Export object
      return device;
    };

    /**
     * @description:  获取唯一ID
     */
    this.getUuid = function() {
      const timeStamp = new Date().getTime();
      return (
        "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
          const r = (Math.random() * 16) | 0,
            v = c == "x" ? r : (r & 0x3) | 0x8;
          return v.toString(16);
        }) +
        "-" +
        timeStamp
      );
    };

    /**
     * @description:  base64编码
     */
    this.b64EncodeUnicode = function(str) {
      try {
        return btoa(
          encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(
            match,
            p1
          ) {
            return String.fromCharCode("0x" + p1);
          })
        );
      } catch (e) {
        return str;
      }
    };
    /**
     * @description:获取cookie
     */
    this.getCookie = function(name) {
      let arr;
      const reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
      if (document.cookie.match(reg)) {
        arr = document.cookie.match(reg);
        return unescape(arr[2]);
      }
      return "";
    };

    /**
     * @description: 封装简易的ajax请求
     * @param method 请求类型(大写) GET/POST
     * @param url 请求URL
     * @param param 请求参数
     * @param successCallback 成功回调方法
     * @param failCallback 失败回调方法
     */
    this.ajax = function(method, url, param, successCallback, failCallback) {
      const xmlHttp = window.XMLHttpRequest
        ? new XMLHttpRequest()
        : new ActiveXObject("Microsoft.XMLHTTP");
      xmlHttp.open(method, url, true);
      xmlHttp.setRequestHeader(
        "Content-Type",
        "application/x-www-form-urlencoded"
      );
      xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
          const res = JSON.parse(xmlHttp.responseText);
          typeof successCallback == "function" && successCallback(res);
        } else {
          typeof failCallback == "function" && failCallback();
        }
      };
      xmlHttp.send("data=" + JSON.stringify(param));
    };
    /**
     * @description:获取用户的唯一标识
     */
    this.getCustomerKey = function() {
      const customerKey = utils.getUuid();
      const monitorCustomerKey = utils.getCookie("monitorCustomerKey");
      if (!monitorCustomerKey) {
        const extraTime = 30 * 24 * 3600 * 1000; // cookie 30天后过期时间
        // const extraTime = 60 * 30 * 24 * 3600 * 1000; // cookie 5年后过期时间
        const exp = new Date();
        exp.setTime(exp.getTime() + extraTime);
        // if (MAIN_DOMAIN) {
        // 	document.cookie = "monitorCustomerKey=" + customerKey + ";Path=/;domain=" + MAIN_DOMAIN + ";expires=" +
        // 		exp.toGMTString()
        // } else {
        document.cookie =
          "monitorCustomerKey=" +
          customerKey +
          ";Path=/;expires=" +
          exp.toGMTString();
        // }
        monitorCustomerKey = customerKey;
      }
      return monitorCustomerKey;
    };
    /**
     * @description:获取页面的唯一标识
     */
    this.getPageKey = function() {
      const pageKey = utils.getUuid();
      const reg = /^[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}-\d{13}$/;
      if (!localStorage.monitorPageKey) {
        localStorage.monitorPageKey = pageKey;
      } else if (!reg.test(localStorage.monitorPageKey)) {
        localStorage.monitorPageKey = pageKey;
      }
      return localStorage.monitorPageKey;
    };
    /**
     * @description:设置页面的唯一标识
     */
    this.setPageKey = function() {
      localStorage.monitorPageKey = utils.getUuid();
    };

    /**
     * 重写页面的onload事件
     */
    this.addLoadEvent = function(func) {
      const oldOnload = window.onload; //把现在有window.onload事件处理函数的值存入变量oldonload。
      if (typeof window.onload != "function") {
        //如果这个处理函数还没有绑定任何函数，就像平时那样把新函数添加给它
        window.onload = func;
      } else {
        //如果在这个处理函数上已经绑定了一些函数。就把新函数追加到现有指令的末尾
        window.onload = function() {
          oldOnload();
          func();
        };
      }
    };

    /**
     * @description:深拷贝方法
     */
    // 注意: 如果对象里边包含function, 则对function的拷贝依然是浅拷贝
    this.encryptObj = function(o) {
      if (o instanceof Array) {
        const n = [];
        for (let i = 0; i < o.length; ++i) {
          n[i] = this.encryptObj(o[i]);
        }
        return n;
      } else if (o instanceof Object) {
        const n = {};
        for (const i in o) {
          n[i] = this.encryptObj(o[i]);
        }
        return n;
      }
      o = o + "";
      if (o.length > 8) {
        o = o.substring(0, 4) + "****" + o.substring(o.length - 3, o.length);
      }
      return o;
    };
  }

  function siftAndMakeUpMessage(
    infoType,
    origin_errorMsg,
    origin_url,
    origin_lineNumber,
    origin_columnNumber,
    origin_errorObj
  ) {
    // 记录js错误前，检查一下url记录是否变化
    checkUrlChange();
    const errorMsg = origin_errorMsg ? origin_errorMsg : "";
    let errorObj = origin_errorObj ? origin_errorObj : "";
    let errorType = "";
    if (errorMsg) {
      if (typeof errorObj === "string") {
        errorType = errorObj.split(": ")[0].replace('"', "");
      } else {
        const errorStackStr = JSON.stringify(errorObj);
        errorType = errorStackStr.split(": ")[0].replace('"', "");
      }
    }
    console.log(infoType, errorType + ": " + errorMsg, errorObj);
    const javaScriptErrorInfo = new JavaScriptErrorInfo(
      JS_ERROR,
      infoType,
      errorType + ": " + errorMsg,
      errorObj
    );
    javaScriptErrorInfo.handleLogInfo(JS_ERROR, javaScriptErrorInfo);
  }

  // 通用属性
  function setCommonProperty() {
    // 发生时间
    this.h = new Date().getTime();
    // 设备名称
    this.X = DEVICE_INFO.deviceName;
    // 项目ID
    this.a = MONITOR_ID;
    // 系统版本
    this.j =
      DEVICE_INFO.os +
      (DEVICE_INFO.osVersion ? " " + DEVICE_INFO.osVersion : "");
    // 浏览器名称
    this.Y = DEVICE_INFO.browserName;
    // 浏览器版本
    this.Z = DEVICE_INFO.browserVersion;
    // 自定义key
    this.c = utils.getCustomerKey();
    // 当前URL
    this.d = utils.b64EncodeUnicode(
      encodeURIComponent(window.location.href)
    );
    // userAgent
    this.m = userAgent();
    // 消息ID
    this.l = utils.getUuid();
    // SDK 版本
    this.n = version;
  }

  // userAgent
  function userAgent(params) {
    return window.navigator.userAgent;
  }

  // 日志基类, 用于其他日志的继承
  function MonitorBaseInfo() {
    this.handleLogInfo = function(type, logInfo) {
      let tempString = localStorage[type] ? localStorage[type] : "";
      switch (type) {
        case CUSTOMER_PV:
          localStorage[CUSTOMER_PV] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case LOAD_PAGE:
          localStorage[LOAD_PAGE] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case ELE_BEHAVIOR:
          localStorage[ELE_BEHAVIOR] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case RESOURCE_LOAD:
          localStorage[RESOURCE_LOAD] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case JS_ERROR:
          localStorage[JS_ERROR] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case HTTP_LOG:
          localStorage[HTTP_LOG] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        case CUSTOMIZE_BEHAVIOR:
          localStorage[CUSTOMIZE_BEHAVIOR] =
            tempString + JSON.stringify(logInfo) + "￥￥￥";
          break;
        default:
          break;
      }
    };
  }

  // JS错误日志，继承于日志基类MonitorBaseInfo
  function JavaScriptErrorInfo(uploadType, infoType, errorMsg, errorStack) {
    setCommonProperty.apply(this);

    // 上报类型
    this.b = uploadType;
    // 信息类型
    this.G = infoType;
    // 错误信息
    this.H = errorMsg;
    // 错误堆栈信息
    this.I = errorStack;
    // 用于区分页面，所对应唯一的标识，每个新页面对应一个值
    this.k = utils.getPageKey();
  }
  JavaScriptErrorInfo.prototype = new MonitorBaseInfo();

  // 页面静态资源加载错误统计，继承于日志基类MonitorBaseInfo
  function ResourceLoadInfo(uploadType, url, elementType, status) {
    setCommonProperty.apply(this);
    // 上报类型
    this.b = uploadType;
    // 静态资源类型
    this.T = elementType;
    // 报错的url
    this.S = utils.b64EncodeUnicode(encodeURIComponent(url));
    // 资源加载状态： 0/失败、1/成功
    this.L = status;
  }
  ResourceLoadInfo.prototype = new MonitorBaseInfo();

  // 接口请求日志，继承于日志基类MonitorBaseInfo
  function HttpLogInfo(
    uploadType,
    simpleUrl,
    url,
    status,
    statusText,
    statusResult,
    responseText,
    currentTime,
    loadTime
  ) {
    console.log(responseText, "responseText");
    setCommonProperty.apply(this);
    // 上传类型
    this.b = uploadType;
    // url
    this.J = simpleUrl;
    // 请求地址
    this.K = utils.b64EncodeUnicode(encodeURIComponent(url));

    // 接口状态
    this.L = status;

    // 状态描述
    this.M = statusText;

    // 区分发起和返回状态
    this.N = statusResult;

    // 请求参数的JSON字符串
    this.O = "";

    // 返回的结果JSON字符串
    this.P = utils.b64EncodeUnicode(responseText);

    // 客户端发送时间
    this.h = currentTime;

    // 接口请求耗时
    this.Q = loadTime;
  }
  HttpLogInfo.prototype = new MonitorBaseInfo();

  // 用户访问行为日志(PV)
  function CustomerPV(uploadType, loadType, loadTime, newStatus) {
    setCommonProperty.apply(this);
    // 上报类型
    this.b = uploadType;
    // 用以区分首次加载
    this.o = loadType;
    // 加载时间
    this.Q = loadTime;
    // 是否为新用户
    this.R = newStatus;
    // 页面唯一id
    this.k = utils.getPageKey();

    // 收集信息
    // 标题
    if (user_id) {
      this.NNWID = localStorage.getItem("NNWID");
    }
    this.U = document.title;
    // 来源
    this.V = utils.b64EncodeUnicode(
      encodeURIComponent(document.referrer)
    );
    // 屏幕分辨率
    this.W = `${window.screen.height}x${window.screen.width}`;
    // IP 地区等定理位置参数 通过服务端获取
    // 访问时间 最好去取服务器时间
  }
  CustomerPV.prototype = new MonitorBaseInfo();

  // 用户加载页面的信息日志
  function LoadPageInfo(
    uploadType,
    loadType,
    loadPage,
    domReady,
    redirect,
    lookupDomain,
    ttfb,
    request,
    loadEvent,
    appcache,
    unloadEvent,
    connect
  ) {
    setCommonProperty.apply(this);
    this.b = uploadType;
    this.o = loadType;
    this.loadPage = loadPage;
    this.domReady = domReady;
    this.redirect = redirect;
    this.lookupDomain = lookupDomain;
    this.ttfb = ttfb;
    this.request = request;
    this.loadEvent = loadEvent;
    this.appcache = appcache;
    this.unloadEvent = unloadEvent;
    this.connect = connect;
  }
  LoadPageInfo.prototype = new MonitorBaseInfo();

  // 用户行为日志，继承于日志基类MonitorBaseInfo
  function BehaviorInfo(
    uploadType,
    behaviorType,
    className,
    placeholder,
    inputValue,
    tagName,
    innerText
  ) {
    setCommonProperty.apply(this);
    this.b = uploadType;
    this.A = behaviorType;
    this.B = utils.b64EncodeUnicode(className);
    this.p = utils.b64EncodeUnicode(placeholder);
    this.D = utils.b64EncodeUnicode(inputValue);
    this.E = tagName;
    this.F = utils.b64EncodeUnicode(encodeURIComponent(innerText));
  }
  BehaviorInfo.prototype = new MonitorBaseInfo();

  // 上传拓展日志信息的入口
  function ExtendBehaviorInfo(index, action, label, description) {
    // 日志发生时间
    this.h = new Date().getTime();
    this.e = index;
    this.f = action;
    this.g = label;
    this.b = CUSTOMIZE_BEHAVIOR;
    this.i = description;
    this.d = utils.b64EncodeUnicode(
      encodeURIComponent(window.location.href)
    );
  }

  ExtendBehaviorInfo.prototype = new MonitorBaseInfo();

  /**
   * @description: 检测url
   */
  function checkUrlChange() {
    // 如果是单页应用， 只更改url
    const webLocation = window.location.href.split("?")[0].replace("#", "");
    // 如果url变化了， 就把更新的url记录为 defaultLocation, 重新设置pageKey
    // console.log(defaultLocation, webLocation, "checkUrlChange");

    if (defaultLocation != webLocation) {
      recordPV();
      defaultLocation = webLocation;
    }
  }

  /**
   * @description: 用户访问记录监控
   * @return:
   */
  function recordPV(params) {
    // 设置页码ID
    utils.setPageKey();
    let loadType = "load";
    if (resourcesObj) {
      if (resourcesObj[0] && resourcesObj[0].type === "navigate") {
        loadType = "load";
      } else {
        loadType = "reload";
      }
    }

    // 判断是否是新用户  开始
    const customerKey = utils.getCustomerKey();
    let newStatus = "";
    const customerKeyArr = customerKey ? customerKey.match(/\d{13}/g) : [];
    if (customerKeyArr && customerKeyArr.length > 0) {
      const tempTime = parseInt(customerKeyArr[0], 10);
      const currentTime = new Date().getTime();
      if (currentTime - tempTime > 1000) {
        newStatus = "old";
      } else {
        newStatus = "new";
      }
    }
    console.log(loadType, newStatus, customerKeyArr);

    // 判断是否是新用户  结束
    const customerPv = new CustomerPV(CUSTOMER_PV, loadType, 0, newStatus);
    customerPv.handleLogInfo(CUSTOMER_PV, customerPv);
  }

  /**
   * 用户行为记录监控
   * @param project 项目详情
   */
  function recordBehavior(project) {
    // 行为记录开关
    if (project && project.record && project.record == 1) {
      // 记录行为前，检查一下url记录是否变化
      checkUrlChange();
      // 记录用户点击元素的行为数据
      document.onclick = function(e) {
        let className = "";
        let placeholder = "";
        let inputValue = "";
        let tagName = e.target.tagName;
        let innerText = "";
        if (e.target.tagName != "svg" && e.target.tagName != "use") {
          className = e.target.className;
          placeholder = e.target.placeholder || "";
          inputValue = e.target.value || "";
          innerText = e.target.innerText
            ? e.target.innerText.replace(/\s*/g, "")
            : "";
          // 如果点击的内容过长，就截取上传
          if (innerText.length > 200)
            innerText =
              innerText.substring(0, 100) +
              "... ..." +
              innerText.substring(innerText.length - 99, innerText.length - 1);
          innerText = innerText.replace(/\s/g, "");
        }
        console.log(className, placeholder, inputValue, tagName, innerText);
        const behaviorInfo = new BehaviorInfo(
          ELE_BEHAVIOR,
          "click",
          className,
          placeholder,
          inputValue,
          tagName,
          innerText
        );
        behaviorInfo.handleLogInfo(ELE_BEHAVIOR, behaviorInfo);
      };
    }
  }

  /**
   * 页面JS错误监控
   */
  function recordJavaScriptError() {
    // 重写console.error, 可以捕获更全面的报错信息
    const oldError = console.error;
    console.error = function(tempErrorMsg) {
      const errorMsg = (arguments[0] && arguments[0].message) || tempErrorMsg;
      const lineNumber = 0;
      const columnNumber = 0;
      const errorObj = arguments[0] && arguments[0].stack;
      if (!errorObj) {
        siftAndMakeUpMessage(
          "console_error",
          errorMsg,
          WEB_LOCATION,
          lineNumber,
          columnNumber,
          "CustomizeError: " + errorMsg
        );
      } else {
        siftAndMakeUpMessage(
          "console_error",
          errorMsg,
          WEB_LOCATION,
          lineNumber,
          columnNumber,
          errorObj
        );
      }
      return oldError.apply(console, arguments);
    };
    // 重写 onerror 进行jsError的监听
    window.onerror = function(
      errorMsg,
      url,
      lineNumber,
      columnNumber,
      errorObj
    ) {
      jsMonitorStarted = true;
      const errorStack = errorObj ? errorObj.stack : null;
      siftAndMakeUpMessage(
        "on_error",
        errorMsg,
        url,
        lineNumber,
        columnNumber,
        errorStack
      );
    };
    window.onunhandledrejection = function(e) {
      let errorMsg = "";
      let errorStack = "";
      if (typeof e.reason === "object") {
        errorMsg = e.reason.message;
        errorStack = e.reason.stack;
      } else {
        errorMsg = e.reason;
        errorStack = "";
      }
      siftAndMakeUpMessage(
        "on_error",
        errorMsg,
        WEB_LOCATION,
        0,
        0,
        "UncaughtInPromiseError: " + errorStack
      );
    };
  }

  /**
   * 用户加载页面信息监控
   * @param project 项目详情
   */
  function recordLoadPage() {
    utils.addLoadEvent(function() {
      setTimeout(function() {
        if (resourcesObj) {
          let loadType = "load";
          if (resourcesObj[0] && resourcesObj[0].type === "navigate") {
            loadType = "load";
          } else {
            loadType = "reload";
          }

          const t = timingObj;
          const loadPageInfo = new LoadPageInfo(LOAD_PAGE);
          // 页面加载类型， 区分第一次load还是reload
          loadPageInfo.loadType = loadType;

          //【重要】页面加载完成的时间
          //【原因】这几乎代表了用户等待页面可用的时间
          loadPageInfo.loadPage = t.loadEventEnd - t.navigationStart;

          //【重要】解析 DOM 树结构的时间
          //【原因】反省下你的 DOM 树嵌套是不是太多了！
          loadPageInfo.domReady = t.domComplete - t.responseEnd;

          //【重要】重定向的时间
          //【原因】拒绝重定向！比如，http://example.com/ 就不该写成 http://example.com
          loadPageInfo.redirect = t.redirectEnd - t.redirectStart;

          //【重要】DNS 查询时间
          //【原因】DNS 预加载做了么？页面内是不是使用了太多不同的域名导致域名查询的时间太长？
          // 可使用 HTML5 Prefetch 预查询 DNS ，见：[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)
          loadPageInfo.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

          //【重要】读取页面第一个字节的时间
          //【原因】这可以理解为用户拿到你的资源占用的时间，加异地机房了么，加CDN 处理了么？加带宽了么？加 CPU 运算速度了么？
          // TTFB 即 Time To First Byte 的意思
          // 维基百科：https://en.wikipedia.org/wiki/Time_To_First_Byte
          loadPageInfo.ttfb = t.responseStart - t.navigationStart;

          //【重要】内容加载完成的时间
          //【原因】页面内容经过 gzip 压缩了么，静态资源 css/js 等压缩了么？
          loadPageInfo.request = t.responseEnd - t.requestStart;

          //【重要】执行 onload 回调函数的时间
          //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了，考虑过延迟加载、按需加载的策略么？
          loadPageInfo.loadEvent = t.loadEventEnd - t.loadEventStart;

          // DNS 缓存时间
          loadPageInfo.appcache = t.domainLookupStart - t.fetchStart;

          // 卸载页面的时间
          loadPageInfo.unloadEvent = t.unloadEventEnd - t.unloadEventStart;

          // TCP 建立连接完成握手的时间
          loadPageInfo.connect = t.connectEnd - t.connectStart;

          loadPageInfo.handleLogInfo(LOAD_PAGE, loadPageInfo);
        }
        // 此方法有漏洞，暂时先注释掉
        // performanceGetEntries();
      }, 1000);
    });
  }

  /**
   * 监控页面静态资源加载报错
   */
  function recordResourceError() {
    // 当浏览器不支持 window.performance.getEntries 的时候，用下边这种方式
    window.addEventListener(
      "error",
      function(e) {
        let typeName = e.target.localName;
        console.log(typeName, "typeName");
        let sourceUrl = "";
        if (typeName === "link") {
          sourceUrl = e.target.href;
        } else if (typeName === "script" || typeName === "img") {
          sourceUrl = e.target.src;
        }
        console.log(sourceUrl);
        const resourceLoadInfo = new ResourceLoadInfo(
          RESOURCE_LOAD,
          sourceUrl,
          typeName,
          "0"
        );
        console.log(resourceLoadInfo, "resourceLoadInfo");
        resourceLoadInfo.handleLogInfo(RESOURCE_LOAD, resourceLoadInfo);
      },
      true
    );
  }

  /**
   * 页面接口请求监控
   */
  function recordHttpLog() {
    // 监听ajax的状态
    function ajaxEventTrigger(event) {
      const ajaxEvent = new CustomEvent(event, {
        detail: this
      });
      window.dispatchEvent(ajaxEvent);
    }
    const oldXHR = window.XMLHttpRequest;

    function newXHR() {
      const realXHR = new oldXHR();
      realXHR.addEventListener(
        "abort",
        function() {
          ajaxEventTrigger.call(this, "ajaxAbort");
        },
        false
      );
      realXHR.addEventListener(
        "error",
        function() {
          ajaxEventTrigger.call(this, "ajaxError");
        },
        false
      );
      realXHR.addEventListener(
        "load",
        function() {
          ajaxEventTrigger.call(this, "ajaxLoad");
        },
        false
      );
      realXHR.addEventListener(
        "loadstart",
        function() {
          ajaxEventTrigger.call(this, "ajaxLoadStart");
        },
        false
      );
      realXHR.addEventListener(
        "progress",
        function() {
          ajaxEventTrigger.call(this, "ajaxProgress");
        },
        false
      );
      realXHR.addEventListener(
        "timeout",
        function() {
          ajaxEventTrigger.call(this, "ajaxTimeout");
        },
        false
      );
      realXHR.addEventListener(
        "loadend",
        function() {
          ajaxEventTrigger.call(this, "ajaxLoadEnd");
        },
        false
      );
      realXHR.addEventListener(
        "readystatechange",
        function() {
          ajaxEventTrigger.call(this, "ajaxReadyStateChange");
        },
        false
      );
      // 此处的捕获的异常会连日志接口也一起捕获，如果日志上报接口异常了，就会导致死循环了。
      // realXHR.onerror = function () {
      // siftAndMakeUpMessage("Uncaught FetchError: Failed to ajax", WEB_LOCATION, 0, 0, {});
      // }
      return realXHR;
    }

    function handleHttpResult(i, tempResponseText) {
      if (!timeRecordArray[i] || timeRecordArray[i].uploadFlag === true) {
        return;
      }
      let responseText = "";
      if (tempResponseText && responseText.length < 300) {
        try {
          responseText = tempResponseText
            ? JSON.stringify(utils.encryptObj(JSON.parse(tempResponseText)))
            : "";
        } catch (e) {
          responseText = "";
        }
      } else {
        responseText = "data is too long";
      }
      const simpleUrl = timeRecordArray[i].simpleUrl;
      const currentTime = new Date().getTime();
      const url = timeRecordArray[i].event.detail.responseURL;
      const status = timeRecordArray[i].event.detail.status;
      const statusText = timeRecordArray[i].event.detail.statusText;
      const loadTime = currentTime - timeRecordArray[i].timeStamp;
      if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return;
      const httpLogInfoStart = new HttpLogInfo(
        HTTP_LOG,
        simpleUrl,
        url,
        status,
        statusText,
        "发起请求",
        "",
        timeRecordArray[i].timeStamp,
        0
      );
      console.log(httpLogInfoStart, "httpLogInfoStart");
      httpLogInfoStart.handleLogInfo(HTTP_LOG, httpLogInfoStart);
      const httpLogInfoEnd = new HttpLogInfo(
        HTTP_LOG,
        simpleUrl,
        url,
        status,
        statusText,
        "请求返回",
        responseText,
        currentTime,
        loadTime
      );
      httpLogInfoEnd.handleLogInfo(HTTP_LOG, httpLogInfoEnd); // 当前请求成功后就，就将该对象的uploadFlag设置为true, 代表已经上传了
      console.log(httpLogInfoEnd, "httpLogInfoStart");
      // 当前请求成功后就，就将该对象的uploadFlag设置为true, 代表已经上传了
      timeRecordArray[i].uploadFlag = true;
    }
    const timeRecordArray = [];
    window.XMLHttpRequest = newXHR;
    window.addEventListener("ajaxLoadStart", function(e) {
      const tempObj = {
        timeStamp: new Date().getTime(),
        event: e,
        simpleUrl: window.location.href.split("?")[0].replace("#", ""),
        uploadFlag: false
      };
      timeRecordArray.push(tempObj);
    });
    window.addEventListener("ajaxLoadEnd", function() {
      for (let i = 0; i < timeRecordArray.length; i++) {
        // uploadFlag == true 代表这个请求已经被上传过了
        if (timeRecordArray[i].uploadFlag === true) continue;
        if (timeRecordArray[i].event.detail.status > 0) {
          const rType = (
            timeRecordArray[i].event.detail.responseType + ""
          ).toLowerCase();
          if (rType === "blob") {
            (function(index) {
              const reader = new FileReader();
              reader.onload = function() {
                const responseText = reader.result; //内容就在这里
                handleHttpResult(index, responseText);
              };
              try {
                reader.readAsText(
                  timeRecordArray[i].event.detail.response,
                  "utf-8"
                );
              } catch (e) {
                handleHttpResult(
                  index,
                  timeRecordArray[i].event.detail.response + ""
                );
              }
            })(i);
          } else {
            const responseText = timeRecordArray[i].event.detail.responseText;
            handleHttpResult(i, responseText);
          }
        }
      }
    });
  }

  /**
   * 添加一个定时器，进行数据的上传
   * 200毫秒钟进行一次URL是否变化的检测
   * 8秒钟进行一次数据的检查并上传; PS: 这个时间有可能跟后台服务的并发量有着直接，谨慎设置
   */
  function init() {
    try {
      // 用户访问记录监控
      recordPV();
      // 监控静态资源报错
      recordResourceError();
      // 用户加载页面信息监控
      recordLoadPage();
      // 监控JS错误
      recordJavaScriptError();
      // 监控http请求
      recordHttpLog();
      // 用户行为记录监控 @ 目前只有click，暂时注释
      // recordBehavior({ record: 1 });

      let timeCount = 0;
      const typeList = [
        JS_ERROR,
        HTTP_LOG,
        RESOURCE_LOAD,
        CUSTOMER_PV,
        LOAD_PAGE,
        ELE_BEHAVIOR,
        CUSTOMIZE_BEHAVIOR
      ];
      // const typeList = [];
      setInterval(() => {
        // 检测URL变化
        checkUrlChange();
        if (timeCount >= 40) {
          let logInfo = "";
          for (let i = 0; i < typeList.length; i++) {
            logInfo += localStorage[typeList[i]] || "";
          }
          // sendBeacon
          // const blob = new Blob([logInfo], {
          //   type: "application/x-www-form-urlencoded"
          // });
          // console.log(blob,'blob');
          // navigator.sendBeacon('http://127.0.0.1:3000/app', blob)

          if (logInfo.length > 0) {
            utils.ajax(
              "POST",
              "http://127.0.0.1:3000/upLog",
              {
                logInfo: logInfo
              },
              () => {
                for (let i = 0; i < typeList.length; i++) {
                  localStorage[typeList[i]] = "";
                }
              },
              () => {
                for (let i = 0; i < typeList.length; i++) {
                  localStorage[typeList[i]] = "";
                }
              }
            );

            timeCount = 0;
          }
        }
        timeCount++;
      }, 200);
    } catch (e) {
      console.error("监控代码异常，捕获", e);
    }
  }

  // 初始化
  init();
  // nma = Nuo Monitor Analytics/诺诺监控分析
  return {
    V: version,
    info: "It's Works!",
    /**
     * @description: 自定义上传
     * @param {String} index  标志位 项目内唯一的id
     * @param {String} action  交互行为名称{ click，touch，play，select 等}
     * @param {String} label  事件信息
     * @param {String} description  详细描述
     * @return:
     */
    _usup: (index, action, label, description) => {
      if (!index || !action) {
        if (!index) console.warn("标志位不能为空");
        if (!action) console.warn("交互行为不能为空");
        return false;
      }

      const extendBehaviorInfo = new ExtendBehaviorInfo(
        index,
        action,
        label,
        description
      );

      extendBehaviorInfo.handleLogInfo(CUSTOMIZE_BEHAVIOR, extendBehaviorInfo);
    }
  };
})(window, document, window.monitorConf);
